#include "CodeGenerator.h"

namespace LALG
{

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  generate
 * Description:  Gera o código de saída
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::generate(ofstream &of)
{
    if(!errors.happened)
    {
        // Código principal
        objCode.push_back("INPP");
        generateCode(mainCode.get());
        objCode.push_back("PARA");

        // Código dos procedimentos
        for(ProcPtr &ptr : update)
        {
            if(!ptr.name.compare("__MAIN__"))
                objCode[ptr.addr].append(to_string(mainAddr));
            else
                objCode[ptr.addr].append(to_string(procAddrs[ptr.name]));
        }

        for(int i = 0; i < objCode.size(); i++)
            of << (i+1) << ": " << objCode[i] << endl;
    }
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  generateCode
 * Description:  Gera o código de um procedimento ou da função principal
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::generateCode(Code* currentCode, bool procedure, uint nAllocs)
{
    uint count;

    for(CmdLinePtr piece : *currentCode)
    {
        // Iterador
        auto it = piece->arg.begin();

        // Primeiro argumento
        string var;
        if(it != piece->arg.end())
            var = *it;

        // Endereço
        int addr;

        currentLine++;
        switch(piece->type)
        {
            // Caso declaração de variável
            case VAR:
                objCode.push_back("ALME 1");
                if(procedure)
                    varAddrs["__PROC__"+var] = varAddrs.size();
                else
                    varAddrs[var] = varAddrs.size();
                nAllocs++;
                break;

            // Caso declaração de constante
            case CONST:
                objCode.push_back("ALME 1");
                if(procedure)
                    varAddrs["__PROC__"+var] = varAddrs.size();
                else
                    varAddrs[var] = varAddrs.size();
                nAllocs++;
                it++;
                objCode.push_back("CRCT " + *it);
                generateVariable(var, STORE_VAR);
                break;

            // Caso readln
            case READLN:
                objCode.push_back("LEIT");
                generateVariable(var, STORE_VAR);
                break;

            // Caso writeln
            case WRITELN:
                generateVariable(var);
                objCode.push_back("IMPR");
                break;

            // Caso procedimento
            case PROC:
                objCode.push_back("DSVI ");
                update.push_back(ProcPtr{curLine(), "__MAIN__"});

                // Registra a posição do procedimento
                procAddrs[var] = curLine() + 2;

                count = 0;
                it++;
                for( ; it != piece->arg.end(); it++)
                {
                    objCode.push_back("COPVL");
                    varAddrs["__PROC__"+*it] = varAddrs.size();
                    count++;
                }

                // Gera o código para o procedimento
                generateCode(procedures[var].get(), true, count);
                mainAddr = curLine() + 2;
                break;

            // Caso for uma chamada a um procedimento
            case CALL:
                objCode.push_back("PUSHER " + to_string(piece->arg.size() + curLine() + 3));

                // Parâmetros
                it++;
                for( ; it != piece->arg.end(); it++)
                    generateVariable(*it, PARAM_VAR);

                // Chama procedimento
                objCode.push_back("CHPR ");
                update.push_back(ProcPtr{curLine(), var});
                break;

            // Atribuição
            case ATTR:
                it++;
                generateExpression(it);
                generateVariable(var, STORE_VAR);
                break;

            // If
            case IF:
                generateExpression(it);
                objCode.push_back("DSVF ");
                emptyAddrs.push(curLine());
                break;

            // Else
            case ELSE:
                objCode.push_back("DSVI ");

                addr = emptyAddrs.top();
                emptyAddrs.pop();
                objCode[addr].append(to_string(curLine()+2));

                emptyAddrs.push(curLine());
                break;

            // End if
            case ENDIF:
                addr = emptyAddrs.top();
                emptyAddrs.pop();
                objCode[addr].append(to_string(curLine()+2));
                break;

            // While
            case WHILE:
                emptyAddrs.push(curLine()+1);
                generateExpression(it);
                objCode.push_back("DSVF ");
                emptyAddrs.push(curLine());
                break;

            // End while
            case ENDWHILE:
                addr = emptyAddrs.top();
                emptyAddrs.pop();
                objCode[addr].append(to_string(curLine()+3));

                addr = emptyAddrs.top();
                emptyAddrs.pop();
                objCode.push_back("DSVI " + to_string(addr));
                break;

            // Repeat
            case REPEAT:
                emptyAddrs.push(curLine()+1);
                break;

            // Until
            case UNTIL:
                generateExpression(it);
                addr = emptyAddrs.top();
                objCode.push_back("DSVF " + to_string(addr+1));
                break;
        }
    }

    // Se for procedimento, encerrá-lo
    if(procedure)
    {
        if(nAllocs > 0)
            objCode.push_back("DESM " + to_string(nAllocs));
        objCode.push_back("RTPR");

        for(auto it = varAddrs.begin(); it != varAddrs.end(); it++)
            if(it->first.find_first_of("__PROC__") != string::npos)
                varAddrs.erase(it);
    }
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  generateExpression
 * Description:  Gera o código de uma expressão
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::generateExpression(list<string>::iterator &it)
{
    string s = *it;
    it++;

    // Número
    if(isdigit(s[0]))
        objCode.push_back("CRCT " + s);

    // Variável
    else if(isalpha(s[0]) || s[0] == '_')
        generateVariable(s);

    // Soma
    else if(!s.compare("+"))
    {
        generateExpression(it);
        generateExpression(it);
        objCode.push_back("SOMA");
    }

    // Diferença
    else if(!s.compare("-"))
    {
        generateExpression(it);
        generateExpression(it);
        objCode.push_back("SUBT");
    }

    // Multiplicação
    else if(!s.compare("*"))
    {
        generateExpression(it);
        generateExpression(it);
        objCode.push_back("MULT");
    }

    // Divisão
    else if(!s.compare("/"))
    {
        generateExpression(it);
        generateExpression(it);
        objCode.push_back("DIVI");
    }

    // Negação
    else if(!s.compare("!"))
    {
        objCode.push_back("CRCT 0");
        generateExpression(it);
        objCode.push_back("SUBT");
    }

    // Menor
    else if(!s.compare("<"))
    {
        generateExpression(it);
        generateExpression(it);
        objCode.push_back("CPME");
    }

    // Maior
    else if(!s.compare(">"))
    {
        generateExpression(it);
        generateExpression(it);
        objCode.push_back("CPMA");
    }

    // Igualdade
    else if(!s.compare("="))
    {
        generateExpression(it);
        generateExpression(it);
        objCode.push_back("CPIG");
    }

    // Diferença
    else if(!s.compare("<>"))
    {
        generateExpression(it);
        generateExpression(it);
        objCode.push_back("CDES");
    }

    // Maior ou igual
    else if(!s.compare(">="))
    {
        generateExpression(it);
        generateExpression(it);
        objCode.push_back("CMAI");
    }

    // Menor ou igual
    else if(!s.compare("<="))
    {
        generateExpression(it);
        generateExpression(it);
        objCode.push_back("CPMI");
    }
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  generateVariable
 * Description:  Gera o valor de uma variável
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::generateVariable(const string &name, int type)
{
    auto it = varAddrs.find("__PROC__"+name);
    if(it == varAddrs.end())
        it = varAddrs.find(name);

    // Armazenamento
    if(type == STORE_VAR)
        objCode.push_back("ARMZ " + to_string(it->second));

    // Carregamento
    else if(type == LOAD_VAR)
        objCode.push_back("CRVL " + to_string(it->second));

    // Parâmetro
    else
        objCode.push_back("PARAM " + to_string(it->second));
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  addCommand
 * Description:  Adiciona um comando
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::addCommand(CmdLinePtr cmd)
{
    if(!errors.happened)
    {
        code->push_back(cmd);
//        cout << cmd->type << " ";
//        for(string s : cmd->arg)
//            cout << s << " ";
//        cout << endl;
    }
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  addConstant
 * Description:  Adiciona uma constante
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::addConstant(const string &name, const VarValue &value)
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = CONST;
    cmd->arg.push_back(name);
    if(value.type == INTEGER)
        cmd->arg.push_back(to_string(value.ivalue));
    else
        cmd->arg.push_back(to_string(value.fvalue));
    addCommand(cmd);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  addVariable
 * Description:  Adiciona uma variável
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::addVariable(const string &name)
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = VAR;
    cmd->arg.push_back(name);
    addCommand(cmd);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  addReadln
 * Description:  Adiciona um readln
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::addReadln(const string &var)
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = READLN;
    cmd->arg.push_back(var);
    addCommand(cmd);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  addWriteln
 * Description:  Adiciona um writeln
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::addWriteln(const string &var)
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = WRITELN;
    cmd->arg.push_back(var);
    addCommand(cmd);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  addProcedure
 * Description:  Adiciona um procedimento
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::addProcedure(const string &name, const VarList &vlist)
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = CALL;
    cmd->arg.push_back(name);
    for(const VarArg &arg : vlist)
        cmd->arg.push_back(arg.name);
    addCommand(cmd);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  addAttribution
 * Description:  Adiciona uma atribuição
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::addAttribution(const string &name, const ExpressionTree &exp)
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = ATTR;
    cmd->arg.push_back(name);
    exp.fill(cmd->arg);
    addCommand(cmd);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  startProcedure
 * Description:  Começa um procedimento
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::startProcedure(const string &name, const VarList &vlist)
{
    if(!errors.happened)
    {
        CmdLinePtr cmd(new CmdLine);
        cmd->type = PROC;
        cmd->arg.push_back(name);
        for(const VarArg &arg : vlist)
            cmd->arg.push_back(arg.name);
        addCommand(cmd);

        CodePtr cod(new Code);
        procedures[name] = cod;
        code = cod.get();
    }
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  endProcedure
 * Description:  Encerra um procedimento
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::endProcedure()
{
    if(!errors.happened)
        code = mainCode.get();
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  startIf
 * Description:  Começa um if
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::startIf(const ExpressionTree &exp)
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = IF;
    exp.fill(cmd->arg);
    addCommand(cmd);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  startElse
 * Description:  Começa um else
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::startElse()
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = ELSE;
    addCommand(cmd);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  startWhile
 * Description:  Começa um while
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::startWhile(const ExpressionTree &exp)
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = WHILE;
    exp.fill(cmd->arg);
    addCommand(cmd);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  endIf
 * Description:  Encerra um if
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::endIf()
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = ENDIF;
    addCommand(cmd);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  endBlock
 * Description:  Encerra um bloco
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::endWhile()
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = ENDWHILE;
    addCommand(cmd);
}


/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  startRepeat
 * Description:  Começa um repeat
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::startRepeat()
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = REPEAT;
    addCommand(cmd);
}

/*
 *--------------------------------------------------------------------------------------
 *       Class:  CodeGenerator
 *      Method:  endRepeat
 * Description:  Termina um repeat
 *--------------------------------------------------------------------------------------
 */
void CodeGenerator::endRepeat(const ExpressionTree &exp)
{
    CmdLinePtr cmd(new CmdLine);
    cmd->type = UNTIL;
    exp.fill(cmd->arg);
    addCommand(cmd);
}

}
